//-
// ==========================================================================
// Copyright 2010 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+

///////////////////////////////////////////////////////////////////////////////
//
// apiMeshGeometryOverride.cpp
//
////////////////////////////////////////////////////////////////////////////////

#include <apiMeshGeometryOverride.h>
#include <apiMeshShape.h>
#include <apiMeshGeom.h>

#include <maya/MGlobal.h>
#include <maya/MUserData.h>
#include <maya/MSelectionList.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MHWGeometry.h>
#include <maya/MShaderManager.h>
#include <maya/MViewport2Renderer.h>

// Custom user data class to attach to render items
class apiMeshUserData : public MUserData
{
public:
	apiMeshUserData()
	: MUserData(true) // let Maya clean up
	, fMessage("")
	, fNumModifications(0)
	{
	}

	virtual ~apiMeshUserData()
	{
	}

	MString fMessage;
	int fNumModifications;
};

// Custom pre-draw callback
static void apiMeshPreDrawCallback(
	MHWRender::MDrawContext& context,
	const MHWRender::MRenderItem* renderItem)
{
	printf("Pre-draw callback triggered for render item with name '%s'\n", renderItem->name().asChar());
}
// Custom post-draw callback
static void apiMeshPostDrawCallback(
	MHWRender::MDrawContext& context,
	const MHWRender::MRenderItem* renderItem)
{
	printf("Post-draw callback triggered for render item with name '%s'\n", renderItem->name().asChar());
}

apiMeshGeometryOverride::apiMeshGeometryOverride(const MObject& obj)
: MPxGeometryOverride(obj)
, fMesh(NULL)
, fMeshGeom(NULL)
{
	// get the real apiMesh object from the MObject
	MStatus status;
	MFnDependencyNode node(obj, &status);
	if (status)
	{
		fMesh = dynamic_cast<apiMesh*>(node.userNode());
	}
}

apiMeshGeometryOverride::~apiMeshGeometryOverride()
{
	fMesh = NULL;
	fMeshGeom = NULL;
}

void apiMeshGeometryOverride::updateDG()
{
	// pull the actual outMesh from the shape
	if (fMesh)
	{
		fMeshGeom = fMesh->meshGeom();
	}
}

void apiMeshGeometryOverride::updateRenderItems(
	const MDagPath& path,
	MHWRender::MRenderItemList& list)
{
	MHWRender::MRenderer* renderer = MHWRender::MRenderer::theRenderer();
	if (!renderer) return;
	const MHWRender::MShaderManager* shaderMgr = renderer->getShaderManager();
	if (!shaderMgr) return;

	// add wire frame item if it's not there
	int index = list.indexOf(
		"apiMeshWire",
		MHWRender::MGeometry::kLines,
		MHWRender::MGeometry::kWireframe);
	if (index < 0)
	{
		static const bool debugShader = false;

		MHWRender::MRenderItem* wireItem = new MHWRender::MRenderItem(
			"apiMeshWire",
			MHWRender::MGeometry::kLines,
			MHWRender::MGeometry::kWireframe,
			false);
		list.append(wireItem);

		MHWRender::MShaderInstance* shader = shaderMgr->getStockShader(
			MHWRender::MShaderManager::k3dSolidShader,
			debugShader ? apiMeshPreDrawCallback : NULL,
			debugShader ? apiMeshPostDrawCallback : NULL);
		if (shader)
		{
			// set color
			static const float theColor[] = {1.0f, 0.0f, 0.0f, 1.0f};
			shader->setParameter("solidColor", theColor);

			// assign shader
			wireItem->setShader(shader);

			// sample debug code
			if (debugShader)
			{
				MStringArray params;
				shader->parameterList(params);
				unsigned int numParams = params.length();
				printf("DEBUGGING SHADER, BEGIN PARAM LIST OF LENGTH %d\n", numParams);
				for (unsigned int i=0; i<numParams; i++)
				{
					printf("ParamName='%s', ParamType=", params[i].asChar());
					switch (shader->parameterType(params[i]))
					{
						case MHWRender::MShaderInstance::kInvalid:
							printf("'Invalid', ");
						break;
						case MHWRender::MShaderInstance::kBoolean:
							printf("'Boolean', ");
						break;
						case MHWRender::MShaderInstance::kInteger:
							printf("'Integer', ");
						break;
						case MHWRender::MShaderInstance::kFloat:
							printf("'Float', ");
						break;
						case MHWRender::MShaderInstance::kFloat2:
							printf("'Float2', ");
						break;
						case MHWRender::MShaderInstance::kFloat3:
							printf("'Float3', ");
						break;
						case MHWRender::MShaderInstance::kFloat4:
							printf("'Float4', ");
						break;
						case MHWRender::MShaderInstance::kFloat4x4Row:
							printf("'Float4x4Row', ");
						break;
						case MHWRender::MShaderInstance::kFloat4x4Col:
							printf("'Float4x4Col', ");
						break;
						case MHWRender::MShaderInstance::kTexture1:
							printf("'1D Texture', ");
						break;
						case MHWRender::MShaderInstance::kTexture2:
							printf("'2D Texture', ");
						break;
						case MHWRender::MShaderInstance::kTexture3:
							printf("'3D Texture', ");
						break;
						case MHWRender::MShaderInstance::kTextureCube:
							printf("'Cube Texture', ");
						break;
						case MHWRender::MShaderInstance::kSampler:
							printf("'Sampler', ");
						break;
						default:
							printf("'Unknown', ");
						break;
					}
					printf("IsArrayParameter='%s'\n", shader->isArrayParameter(params[i]) ? "YES" : "NO");
				}
				printf("END PARAM LIST\n");
			}

			// once assigned, no need to hold on to shader instance
			// NOTE: shader could be held if desired, to update parameters later
			delete shader;
		}
	}

	// add selection item if it's not there
	MHWRender::MRenderItem* selectItem = NULL;
	index = list.indexOf(
		"apiMeshSelection",
		MHWRender::MGeometry::kLines,
		MHWRender::MGeometry::kAll);
	if (index < 0)
	{
		selectItem = new MHWRender::MRenderItem(
			"apiMeshSelection",
			MHWRender::MGeometry::kLines,
			MHWRender::MGeometry::kAll,
			true);
		list.append(selectItem);

		MHWRender::MShaderInstance* shader = shaderMgr->getStockShader( MHWRender::MShaderManager::k3dSolidShader );
		if (shader)
		{
			// set color
			static const float selColor[] = {0.0f, 1.0f, 0.0f, 1.0f};
			shader->setParameter("solidColor", selColor);
			// assign shader
			selectItem->setShader(shader);
			// once assigned, no need to hold on to shader instance
			delete shader;
		}
	}
	else
	{
		selectItem = list.itemAt(index);
	}

	// Update selection item, disable by default
	selectItem->enable(false);
	{
		MStatus status;
		MSelectionList selectedList;
		status = MGlobal::getActiveSelectionList(selectedList);
		if (status)
		{
			MDagPath pathCopy = path;
			do
			{
				if (selectedList.hasItem(pathCopy))
				{
					selectItem->enable(true);
					break;
				}
				status = pathCopy.pop();
			} while(status);
		}
	}

	// Add custom user data to selection item
	apiMeshUserData* myCustomData = dynamic_cast<apiMeshUserData*>(selectItem->customData());
	if (!myCustomData)
	{
		// create the custom data
		myCustomData = new apiMeshUserData();
		myCustomData->fMessage = "I'm custom data!";
		selectItem->setCustomData(myCustomData);
	}
	else
	{
		// modify the custom data
		myCustomData->fNumModifications++;
	}
}

void apiMeshGeometryOverride::populateGeometry(
	const MHWRender::MGeometryRequirements& requirements,
	MHWRender::MGeometry& data)
{
	// compute number of triangles, assume polys are always convex
	unsigned int numTriangles = 0;
	unsigned int totalVerts = 0;
	for (int i=0; i<fMeshGeom->faceCount; i++)
	{
		int numVerts = fMeshGeom->face_counts[i];
		if (numVerts > 2)
		{
			numTriangles += numVerts - 2;
			totalVerts += numVerts;
		}
	}

	// vertex data
	MHWRender::MVertexBuffer* positionBuffer = NULL;
	float* positions = NULL;
	MHWRender::MVertexBuffer* normalBuffer = NULL;
	float* normals = NULL;
	MHWRender::MVertexBuffer* uvBuffer = NULL;
	float* uvs = NULL;
	int numUVs = fMeshGeom->uvcoords.uvcount();

	const MHWRender::MVertexBufferDescriptorList& descList =
		requirements.requirementDescriptors();
	int numVertexReqs = descList.length();
	MHWRender::MVertexBufferDescriptor desc;
	for (int reqNum=0; reqNum<numVertexReqs; reqNum++)
	{
		if (!descList.getDescriptor(reqNum, desc))
		{
			continue;
		}

		switch (desc.semantic())
		{
			case MHWRender::MGeometry::kPosition:
			{
				if (!positionBuffer)
				{
					positionBuffer = data.createVertexBuffer(desc);
					if (positionBuffer)
					{
						positions = (float*)positionBuffer->acquire(totalVerts);
					}
				}
			}
			break;
			case MHWRender::MGeometry::kNormal:
			{
				if (!normalBuffer)
				{
					normalBuffer = data.createVertexBuffer(desc);
					if (normalBuffer)
					{
						normals = (float*)normalBuffer->acquire(totalVerts);
					}
				}
			}
			break;
			case MHWRender::MGeometry::kTexture:
			{
				if (numUVs > 0 && !uvBuffer)
				{
					uvBuffer = data.createVertexBuffer(desc);
					if (uvBuffer)
					{
						uvs = (float*)uvBuffer->acquire(totalVerts);
					}
				}
			}
			break;
			default:
				// do nothing for stuff we don't understand
			break;
		}
	}

	int vid = 0;
	int pid = 0;
	int nid = 0;
	int uvid = 0;
	for (int i=0; i<fMeshGeom->faceCount; i++)
	{
		// ignore degenerate faces
		int numVerts = fMeshGeom->face_counts[i];
		if (numVerts > 2)
		{
			for (int j=0; j<numVerts; j++)
			{
				if (positions)
				{
					MPoint position = fMeshGeom->vertices[fMeshGeom->face_connects[vid]];
					positions[pid++] = (float)position[0];
					positions[pid++] = (float)position[1];
					positions[pid++] = (float)position[2];
				}

				if (normals)
				{
					MVector normal = fMeshGeom->normals[fMeshGeom->face_connects[vid]];
					normals[nid++] = (float)normal[0];
					normals[nid++] = (float)normal[1];
					normals[nid++] = (float)normal[2];
				}

				if (uvs)
				{
					int uvNum = fMeshGeom->uvcoords.uvId(vid);
					float u = 0.0f;
					float v = 0.0f;
					fMeshGeom->uvcoords.getUV(uvNum, u, v);
					uvs[uvid++] = u;
					uvs[uvid++] = v;
				}

				vid++;
			}
		}
		else if (numVerts > 0)
		{
			vid += numVerts;
		}
	}

	if (positions)
	{
		positionBuffer->commit(positions);
	}

	if (normals)
	{
		normalBuffer->commit(normals);
	}

	if (uvs)
	{
		uvBuffer->commit(uvs);
	}

	// index data
	const MHWRender::MRenderItemList& itemList = requirements.renderItemList();
	MHWRender::MIndexBuffer* wireIndexBuffer = NULL; // reuse same index buffer for both wireframe and selected
	int numItems = itemList.length();
	for (int i=0; i<numItems; i++)
	{
		const MHWRender::MRenderItem* item = itemList.itemAt(i);
		if (!item) continue;

		// Enable to debug vertex buffers that are associated with each render item.
		// Can also use to generate indexing better, but we don't need that here.
		// Also debugs custom data on the render item.
		static const bool debugStuff = false;
		if (debugStuff)
		{
			const MHWRender::MVertexBufferDescriptorList& itemBuffers =
				item->requiredVertexBuffers();
			int numBufs = itemBuffers.length();
			MHWRender::MVertexBufferDescriptor desc;
			for (int bufNum=0; bufNum<numBufs; bufNum++)
			{
				if (itemBuffers.getDescriptor(bufNum, desc))
				{
					printf("Buffer Required for Item #%d ('%s'):\n", i, item->name().asChar());
					printf("\tBufferName: %s\n", desc.name().asChar());
					printf("\tDataType: %s (dimension %d)\n", MHWRender::MGeometry::dataTypeString(desc.dataType()).asChar(), desc.dimension());
					printf("\tSemantic: %s\n", MHWRender::MGeometry::semanticString(desc.semantic()).asChar());
					printf("\n");
				}
			}

			// Just print a message for illustration purposes. Note that the custom data is also
			// accessible from the MRenderItem in MPxShaderOverride::draw().
			apiMeshUserData* myCustomData = dynamic_cast<apiMeshUserData*>(item->customData());
			if (myCustomData)
			{
				printf("Custom data on Item #%d: '%s', modified count='%d'\n\n", i, myCustomData->fMessage.asChar(), myCustomData->fNumModifications);
			}
			else
			{
				printf("No custom data on Item #%d\n\n", i);
			}
		}


		if (item->name() == "apiMeshWire" || item->name() == "apiMeshSelection")
		{
			// Wireframe index buffer is same for both wireframe and selected render item
			// so we only compute and allocate it once, but reuse it for both render items
			if (!wireIndexBuffer)
			{
				wireIndexBuffer = data.createIndexBuffer(MHWRender::MGeometry::kUnsignedInt32);
				if (wireIndexBuffer)
				{
					unsigned int* buffer = (unsigned int*)wireIndexBuffer->acquire(2*totalVerts);
					if (buffer)
					{
						int vid = 0;
						int first = 0;
						unsigned int idx = 0;
						for (int faceIdx=0; faceIdx<fMeshGeom->faceCount; faceIdx++)
						{
							// ignore degenerate faces
							int numVerts = fMeshGeom->face_counts[faceIdx];
							if (numVerts > 2)
							{
								first = vid;
								for (int v=0; v<numVerts-1; v++)
								{
									buffer[idx++] = vid++;
									buffer[idx++] = vid;
								}
								buffer[idx++] = vid++;
								buffer[idx++] = first;
							}
							else
							{
								vid += numVerts;
							}
						}

						wireIndexBuffer->commit(buffer);
					}
				}
			}

			// Associate same index buffer with either render item
			if (wireIndexBuffer)
			{
				item->associateWithIndexBuffer(wireIndexBuffer);
			}
		}
		else if (item->primitive() == MHWRender::MGeometry::kTriangles)
		{
			// shaded
			MHWRender::MIndexBuffer* indexBuffer =
				data.createIndexBuffer(MHWRender::MGeometry::kUnsignedInt32);
			if (indexBuffer)
			{
				unsigned int* buffer = (unsigned int*)indexBuffer->acquire(3*numTriangles);
				if (buffer)
				{
					// compute index data for triangulated convex polygons sharing
					// poly vertex data among triangles
					unsigned int base = 0;
					unsigned int idx = 0;
					for (int faceIdx=0; faceIdx<fMeshGeom->faceCount; faceIdx++)
					{
						// ignore degenerate faces
						int numVerts = fMeshGeom->face_counts[faceIdx];
						if (numVerts > 2)
						{
							for (int v=1; v<numVerts-1; v++)
							{
								buffer[idx++] = base;
								buffer[idx++] = base+v;
								buffer[idx++] = base+v+1;
							}
							base += numVerts;
						}
					}

					indexBuffer->commit(buffer);
					item->associateWithIndexBuffer(indexBuffer);
				}
			}
		}
	}
}

void apiMeshGeometryOverride::cleanUp()
{
	fMeshGeom = NULL;
}

